from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.action_chains import ActionChains

from common.decorators import log4gs
from common.functions import get_xpath


class ElementType:
    """label后面跟的元素类型"""
    INPUT = 'input'
    REGION = 'region'
    GRID = 'grid'
    UPLOAD = 'upload'
    DATE_PICKER = 'datePicker'
    DROPDOWN_LIST = 'dropDownList'
    TEXTAREA = 'textarea'


@log4gs
class ElementBaseLabel:
    """
    在一个指定的元素内部，通过label/index获取lable对应的元素。
    要求:label标签后面只有一个标签是div的弟弟节点
    """

    def __init__(self, type_, label=None, index=None, describe=None, date_picker_index=0, **kwargs):
        """
        :param type_: 目标元素种类：
            input：出入框
            region：区域选择器
            grid：网格选择器
            upload：上传文件、照片
            datePicker：日期选择器
            dropDownList：特指下拉后只有一列元素的下拉框
        :param label: 标签名
        :param index: label标签在instance中的序号，instance一般是FloatWindow或者SearchTools实例
        :param describe: 描述
        :param date_picker_index: 日期选择器的序号，针对开始、结束时间放一起的场景

        使用方法：
            ele1 = ElementBaseLabel(ElementType.GRID, label="区域")   # 区域选择器
            ele2 = ElementBaseLabel(ElementType.DATE_PICKER, label="时间", date_picker_index=1)   # 第二个时间输入框
        """
        if not label and not isinstance(index, int):
            try:
                raise ValueError("标签名或者序号至少提供一个")
            except ValueError:
                self.log.error("标签名或者序号至少提供一个", exc_info=True)

        self.type = type_
        self.label = label
        self.index = index
        self.describe = describe
        self.date_picker_index = date_picker_index
        self.kwargs = kwargs

    def __get__(self, instance, owner):
        """
        :param instance: 包含label的元素实例，且该示例必须提供xpath属性，比如弹窗实例
        :param owner: 弹窗类
        :return: target_element，跟在label后面的元素
        """
        self.driver = instance.driver
        self.label_element = None   # tag是label的元素
        self.target_element = None   # self.label_element第一个弟弟元素
        labels = instance.supply.find_elements_by_tag_name('label')
        if not labels:
            try:
                raise NoSuchElementException("xpath是‘{}’的元素里面没有label标签".format(instance.xpath))
            except:
                self.log.error("xpath是‘{}’的元素里面没有label标签".format(instance.xpath), exc_info=True)

        if self.label:
            self.__by_label(instance, labels)
        elif self.index:
            self.__by_index(labels)

        ### 通过label元素的xpath获取下面的div节点
        ### /html/body/div[13]/div[2]/div/div/div[2]/div/ul[1]/li[1]/form/div/div[9]/label
        ### /html/body/div[13]/div[2]/div/div/div[2]/div/ul[1]/li[1]/form/div/div[9]/div
        self.label_xpath = get_xpath(self.driver, self.label_element)
        self.div_element = self.driver.find_element_by_xpath(self.label_xpath[:-5] + 'div')
        if self.type == ElementType.INPUT:
            self.target_element = self.__get_input_element()
        elif self.type == ElementType.REGION:
            self.target_element = self.__get_region_element()
        elif self.type == ElementType.GRID:
            self.target_element = self.__get_grid_element()
        elif self.type == ElementType.UPLOAD:
            self.target_element = self.__get_upload_element()
        elif self.type == ElementType.DATE_PICKER:
            self.target_element = self.__get_date_picker_element()
        elif self.type == ElementType.DROPDOWN_LIST:
            self.target_element = self.__get_dropdown_list_element()
        elif self.type == ElementType.TEXTAREA:
            self.target_element = self.__get_textarea_element()


        if not self.target_element:
            try:
                raise NoSuchElementException("通过标签名：{}，没有找到‘{}’类型的元素".format(self.label, self.type))
            except NoSuchElementException:
                self.log.error("通过标签名：{}，没有找到‘{}’类型的元素".format(self.label, self.type), exc_info=True)

        return self   # .target_element

    def __by_label(self, instance, labels):
        for lab in labels:
            label_text = lab.text.strip().replace(' ', '')
            ### 去掉label里面的中英文冒号
            if ':' in label_text:
                label_text = label_text.replace(':', '')
            if '：' in label_text:
                label_text = label_text.replace('：', '')
            if label_text == self.label:
                self.label_element = lab
                break
        if not self.label_element:
            try:
                raise NoSuchElementException("没有找到label名称是{}的相关元素".format(self.label))
            except NoSuchElementException:
                self.log.error("没有找到label名称是{}的相关元素".format(self.label), exc_info=True)

    def __by_index(self, labels):
        try:
            self.label_element = labels[self.index]
        except Exception:
            self.log.error("通过序号‘{}’没有找到标签元素".format(self.index))

    def __get_input_element(self):
        return self.div_element.find_element_by_class_name('ivu-input')

    def __get_region_element(self):
        return self.div_element.find_element_by_class_name('ivu-cascader-rel')

    def __get_grid_element(self):
        return self.div_element.find_element_by_class_name('ivu-poptip-rel')

    def __get_upload_element(self):
        return self.div_element.find_element_by_class_name('ivu-upload-input')

    def __get_date_picker_element(self):
        datepicker = self.div_element.find_elements_by_class_name('ivu-date-picker')[self.date_picker_index]
        return datepicker.find_element_by_class_name('ivu-input')

    def __get_dropdown_list_element(self):
        return self.div_element.find_element_by_class_name('ivu-select')

    def __get_textarea_element(self):
        return self.div_element.find_element_by_tag_name('textarea')

    def __find_clear_btn(self, ele, class_name):
        """
        删除按钮并不都属于self.target_element元素的后代，因此要递归父元素去找删除元素。如果时间、上传图片
        :param ele: 需要查找的元素
        :param class_name:  删除元素的类名
        :return:
        """
        try:
            clear_btn = ele.find_element_by_class_name(class_name)
            return clear_btn
        except:
            xpath = get_xpath(self.driver, ele)
            if xpath == '/html':
                return None
            self.log.info("Xpath为'{}'的元素下没有找到类名为{}的删除按钮".format(xpath, class_name))
            parent = self.driver.find_element_by_xpath(xpath.rsplit('/', 1)[0])
            return self.__find_clear_btn(parent, class_name)

    def __find_location_not_zero(self, ele):
        """
        向上递归查找location不为{'x': 0, 'y': 0}的元素
        :param ele: 需要判定的元素
        :return:
        """
        loc = ele.location
        if loc['x'] == 0 and loc['y'] == 0:
            xpath = get_xpath(self.driver, ele).rsplit('/', 1)[0]
            parent = self.driver.find_element_by_xpath(xpath)
            return self.__find_location_not_zero(parent)
        else:
            return ele

    def __move_and_clear(self, class_name):
        """
        需要把鼠标移到元素内，出现并点击删除按钮
        :param class_name: 删除按钮的类名
        :return:

        这里分两步：
        1.移动鼠标
        2.找到删除按钮

        对于步骤1：
            上面用到的__get_xxx_element方法中，通过某些类名找到的元素在html层次中比较深，没有location，即这些元素的x,y坐标都是0。
            这样的话用默认偏移量无法找到，自定义偏移量也很难拿捏。因此这里需要解决两个问题：
                1.找到location不为{'x': 0, 'y': 0}的元素ele
                2.相对ele做偏移，把鼠标放进去
        对于步骤2：
            还是因为找到的元素在html层次中比较深，可能删除按钮不属于它的后代，因此要往祖辈的所有后代搜索
        """

        # 移动鼠标
        try:
            not_zero_ele = self.target_element
            loc = not_zero_ele.location
            if loc['x'] == 0 and loc['y'] == 0:
                parent = self.driver.find_element_by_xpath(self.xpath.rsplit('/', 1)[0])
                not_zero_ele = self.__find_location_not_zero(parent)
            self.log.info("从描述是'{}'的元素开始往上查找，坐标不为0的元素坐标为：{}。\n它的xpath是：{}".format(
                self.describe, not_zero_ele.location, get_xpath(self.driver, not_zero_ele)))

            # 偏移量，让鼠标能放到not_zero_ele中，从而出现删除按钮
            x = self.kwargs.get('clear_btn_xoffset')
            y = self.kwargs.get('clear_btn_yoffset')
            x = x if x else 50   # x默认偏移50，向右为正，向左为负
            y = y if y else 15   # y默认偏移15，向下为正，向上为负

            action = ActionChains(self.driver)
            action.move_to_element_with_offset(not_zero_ele, x, y).perform()
        except Exception:
            self.log.error("鼠标移动至描述为'{}'的元素失败".format(self.describe), exc_info=True)

        # 找到删除按钮
        try:
            clear_btn = self.__find_clear_btn(self.target_element, class_name)
            clear_btn.click()
        except Exception:
            self.log.error("整个网页没有找到删除按钮，试试调整偏移量：\
                            在声明元素的时候添加clear_btn_xoffset、clear_btn_yoffset关键字参数".replace(' ', ''))

    @property
    def xpath(self):
        try:
            xpath = get_xpath(self.driver, self.target_element)
            return xpath
        except Exception:
            self.log.error("获取标签元素的xpath失败", exc_info=True)

    def click(self):
        self.target_element.click()

    def send_keys(self, *args):
        self.target_element.send_keys(*args)

    def clear(self):
        """
        ElementBaseLabel类里各种元素删除按钮的类名:
            INPUT：没有删除按钮
            REGION：ivu-icon-ios-close-circle
            GRID：ivu-icon-ios-close-circle
            UPLOAD：ivu-icon-ios-trash-outline（针对图片，其他文件不需要删除，如果想删除，重新上传即可）
            DATE_PICKER：ivu-icon-ios-close-circle
            DROPDOWN_LIST：ivu-icon-ios-close-circle
        """
        if self.type == ElementType.INPUT:
            self.target_element.clear()
        elif self.type in [ElementType.REGION, ElementType.GRID, ElementType.DATE_PICKER, ElementType.DROPDOWN_LIST]:
            self.__move_and_clear('ivu-icon-ios-close-circle')
        elif self.type == ElementType.UPLOAD:
            self.__move_and_clear('ivu-icon-ios-trash-outline')


@log4gs
class Map:
    """地图元素，一般都是用于点击地图获取中心点经纬度"""

    ClassName = 'amap-labels'

    def __init__(self, index=0):
        """
        :param index: 通过类名amap-labels查出所有元素，index就是目标序号

        浏览器终端运行：document.getElementsByClassName('amap-maps')
        """
        if not isinstance(index, int):
            try:
                raise ValueError("地图元素的序号有误")
            except ValueError:
                self.log.error("地图元素的序号有误", exc_info=True)

        self.index = index

    def __get__(self, instance, owner):
        self.driver = instance.driver
        try:
            self.map = self.driver.find_elements_by_class_name(self.ClassName)[self.index]
            return self.map
        except Exception:
            self.log.error("通过类名'{}'没有找到地图".format(self.ClassName))

    def click(self):
        self.map.click()